package vn.vfriends.payment.openid;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.InMemoryConsumerAssociationStore;
import org.openid4java.consumer.InMemoryNonceVerifier;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vn.vfriends.payment.util.Constants;

/**
 *
 * @author tuan@vfriends.vn
 */
public class OpenIdConsumer {

    private static final Logger log = LoggerFactory.getLogger(OpenIdConsumer.class);
    
    private static ConsumerManager consumerManager;

    final static public String YAHOO_ENDPOINT = "https://me.yahoo.com";
    final static public String GOOGLE_ENDPOINT = "https://www.google.com/accounts/o8/id";

    /**
     * Retrieves an instance of the ConsumerManager object. It is static (see
     * note in Class-level JavaDoc comments above) because openid4java likes it
     * that way.
     *
     * Note: if you are planning to debug the code, set the lifespan parameter
     * of the InMemoryNonceVerifier high enough to outlive your debug cycle, or
     * you may notice Nonce Verification errors. Depending on where you are
     * debugging, this might pose an artificial problem for you (of your own
     * making) that has nothing to do with either your code or openid4java.
     *
     * @return ConsumerManager - The ConsumerManager object that handles
     * communication with the openid4java API.
     */
    private static ConsumerManager getConsumerManager() {
        if (consumerManager == null) {
            consumerManager = new ConsumerManager();
            consumerManager.setAssociations(new InMemoryConsumerAssociationStore());
            consumerManager.setNonceVerifier(new InMemoryNonceVerifier(10000));
        }
        return consumerManager;
    }
    /**
     * Perform discovery on the User-Supplied identifier and return the
     * DiscoveryInformation object that results from Association with the OP.
     * This will probably be needed by the caller (stored in Session perhaps?).
     *
     * I'm not thrilled about ConsumerManager being static, but it is very
     * important to openid4java that the ConsumerManager object be the same
     * instance all through a conversation (discovery, auth request, auth
     * response) with the OP. I didn't dig terribly deeply, but suspect that
     * part of the key exchange or the nonce uses the ConsumerManager's hash, or
     * some other instance-specific construct to do its thing.
     *
     * @param userSuppliedIdentifier The User-Supplied identifier. It may
     * already be normalized.
     *
     * @return DiscoveryInformation - The resulting DisoveryInformation object
     * returned by openid4java following successful association with the OP.
     */
    @SuppressWarnings("unchecked")
    public static DiscoveryInformation performDiscoveryOnUserSuppliedIdentifier(String userSuppliedIdentifier) {
        DiscoveryInformation ret = null;
        // ConsumerManager consumerManager = getConsumerManager();
        consumerManager = getConsumerManager();
        try {
            // Perform discover on the User-Supplied Identifier
            List<DiscoveryInformation> discoveries = consumerManager.discover(userSuppliedIdentifier);
            // Pass the discoveries to the associate() method...
            ret = consumerManager.associate(discoveries);
        } catch (DiscoveryException e) {
            String message = "Error occurred during discovery!";
            log.error(message, e);
            throw new RuntimeException(message, e);
        }
        return ret;
    }

    /**
     * Create an OpenID Auth Request, using the DiscoveryInformation object
     * return by the openid4java library.
     *
     * This method also uses the Simple Registration Extension to grant the
     * Relying Party (RP).
     *
     * @param discoveryInformation The DiscoveryInformation that should have
     * been previously obtained from a call to
     * performDiscoveryOnUserSuppliedIdentifier().
     *
     * @param returnToUrl The URL to which the OP will redirect once the
     * authentication call is complete.
     *
     * @return AuthRequest - A "good-to-go" AuthRequest object packed with all
     * kinds of great OpenID goodies for the OpenID Provider (OP). The caller
     * must take this object and forward it on to the OP. Or call
     * processAuthRequest() - part of this Service Class.
     */
    public static AuthRequest createOpenIdAuthRequest(String userSuppliedIdentifier, DiscoveryInformation discoveryInformation, String returnToUrl) {
        AuthRequest ret = null;
        //
        try {
            // Create the AuthRequest object
            ret = consumerManager.authenticate(discoveryInformation, returnToUrl);
            // Create the Simple Registration Request
//            SRegRequest sRegRequest = SRegRequest.createFetchRequest();
//            sRegRequest.addAttribute("email", false);
//            sRegRequest.addAttribute("fullname", false);
            FetchRequest fetch = FetchRequest.createFetchRequest();
            if (userSuppliedIdentifier.startsWith(GOOGLE_ENDPOINT)) {
                fetch.addAttribute("email", "http://axschema.org/contact/email", true);
                fetch.addAttribute("firstName", "http://axschema.org/namePerson/first", true);
                fetch.addAttribute("lastName", "http://axschema.org/namePerson/last", true);
            } else if (userSuppliedIdentifier.startsWith(YAHOO_ENDPOINT)) {
                fetch.addAttribute("email", "http://axschema.org/contact/email", true);
                fetch.addAttribute("fullname", "http://axschema.org/namePerson", true);
            } else { // works for myOpenID
                fetch.addAttribute("fullname", "http://schema.openid.net/namePerson", true);
                fetch.addAttribute("email", "http://schema.openid.net/contact/email", true);
            }
            ret.addExtension(fetch);

        } catch (Exception e) {
            String message = "Exception occurred while building AuthRequest object!";
            log.error(message, e);
            throw new RuntimeException(message, e);
        }
        return ret;
    }

    /**
     * Processes the returned information from an authentication request from
     * the OP.
     *
     * @param discoveryInformation DiscoveryInformation that was created earlier
     * in the conversation (by openid4java). This will need to be verified with
     * openid4java to make sure everything went smoothly and there are no
     * possible problems. This object was probably stored in session and
     * retrieved for use in calling this method.
     *
     * @param pageParameters PageParameters passed to the page handling the
     * return verificaion.
     *
     * @param returnToUrl The "return to" URL that was passed to the OP. It must
     * match exactly, or openid4java will issue a verification failed message in
     * the logs.
     *
     * @return RegistrationModel - null if there was a problem, or a
     * RegistrationModel object, with parameters filled in as compeletely as
     * possible from the information available from the OP. If you are using
     * MyOpenID, most of the time what is returned is from your "Default"
     * profile, so if you need more information returned, make sure your Default
     * profile is completely filled out.
     */
    public static Map<String, String> processReturn(DiscoveryInformation discoveryInformation, Map<String, String[]> parameters) {
        // Verify the Information returned from the OP
        /// This is required according to the spec
        Map<String, String> returnMap = new HashMap<String, String>();
        ParameterList response = new ParameterList(parameters);
        try {
            VerificationResult verificationResult = consumerManager.verify(getReturnToUrl(), response, discoveryInformation);
            Identifier verifiedIdentifier = verificationResult.getVerifiedId();
            if (verifiedIdentifier != null) {
                AuthSuccess authSuccess = (AuthSuccess) verificationResult.getAuthResponse();
                if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
                    FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(AxMessage.OPENID_NS_AX);

                    List emails = fetchResp.getAttributeValues("email");
                    String email = (String) emails.get(0);
                    returnMap.put("email", email);
                    
                    List fullNames = fetchResp.getAttributeValues("fullname");
                    String fullName = (String) fullNames.get(0);
                    
                    if(fullName == null || fullName.length() == 0) {
                        List firstNames = fetchResp.getAttributeValues("firstName");
                        String firstName = (String) firstNames.get(0);
                        
                        List lastNames = fetchResp.getAttributeValues("lastName");
                        String lastName = (String) lastNames.get(0);
                        
                        fullName = firstName + " " + lastName;
                    }
                    returnMap.put("fullName", fullName);
                }
            }
        } catch (Exception e) {
            String message = "Exception occurred while verifying response!";
            log.error(message, e);
            throw new RuntimeException(message, e);
        }
        return returnMap;
    }

    /**
     * Generates the returnToUrl parameter that is passed to the OP. The User
     * Agent (i.e., the browser) will be directed to this page following
     * authentication.
     *
     * @param representedPage The RegistrationPage object whose cover is to be
     * cracked open to get at the raw HttpServlet goodies inside.
     *
     * @return String - the returnToUrl to be used for the authentication
     * request.
     */
    public static String getReturnToUrl() {
        return Constants.BASIC_URL + "/opconsumer?is_return=true";
    }
}
